unit EncodingFrm;

interface

uses
  EncodingRecs,
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VirtualTrees, Vcl.StdCtrls,
  System.ImageList, Vcl.ImgList, PngImageList, Vcl.ExtCtrls;

type
  TEncodingForm = class(TForm)
    Tree: TVirtualStringTree;
    Button1: TButton;
    CancelBtn: TButton;
    PngImageList1: TPngImageList;
    FilterEdit: TEdit;
    FilterTimer: TTimer;
    AllEncodingCheck: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure TreeInitNode(Sender: TBaseVirtualTree; ParentNode,
      Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
    procedure TreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
    procedure TreeNodeDblClick(Sender: TBaseVirtualTree;
      const HitInfo: THitInfo);
    procedure TreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean;
      var ImageIndex: TImageIndex);
    procedure FilterEditChange(Sender: TObject);
    procedure FilterTimerTimer(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure AllEncodingCheckClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
    FArray: TEncodingRecArray;
    FFilter: string;
    FSelected: Integer;
    procedure RebuildTree;
  public
    { Public declarations }
    class function SelectCodePage(AOwner: TComponent; var ACodePage: Integer): Boolean;
    class function SelectEncoding(AOwner: TComponent; var AEncoding: TEncoding): Boolean;

    property Selected: Integer read FSelected;
  end;

implementation

{$R *.dfm}

uses
  StrUtils, System.Generics.Collections, System.Generics.Defaults;

class function TEncodingForm.SelectCodePage(AOwner: TComponent; var ACodePage: Integer): Boolean;
begin
  with TEncodingForm.Create(AOwner) do
  try
    FSelected := ACodePage;
    RebuildTree;

    Result := ShowModal = mrOk;
    if Result then ACodePage := Selected;
  finally
    Free;
  end;
end;

class function TEncodingForm.SelectEncoding(AOwner: TComponent; var AEncoding: TEncoding): Boolean;
var
  CodePage: Integer;
begin
  if Assigned(AEncoding) then CodePage := AEncoding.CodePage;
  Result := SelectCodePage(AOwner, CodePage);
  if Result then AEncoding := TEncoding.GetEncoding(CodePage);
end;

procedure TEncodingForm.FormCreate(Sender: TObject);
begin
  FArray := GetEncodingRecs;
  TArray.Sort<TEncodingRec>(FArray, TComparer<TEncodingRec>.Construct(
    function(const L, R: TEncodingRec): Integer
    begin
      Result := CompareText(L.Display, R.Display);
    end));
end;

procedure TEncodingForm.AllEncodingCheckClick(Sender: TObject);
begin
  FArray := GetEncodingRecs(not AllEncodingCheck.Checked);
  TArray.Sort<TEncodingRec>(FArray, TComparer<TEncodingRec>.Construct(
    function(const L, R: TEncodingRec): Integer
    begin
      Result := CompareText(L.Display, R.Display);
    end));

  RebuildTree;
end;

procedure TEncodingForm.FormDestroy(Sender: TObject);
begin
  SetLength(FArray, 0);
end;

procedure TEncodingForm.FormShow(Sender: TObject);
begin
  ActiveControl := Tree;
  if Tree.SelectedCount > 0 then
    Tree.ScrollIntoView(Tree.GetFirstSelected, True);
end;

procedure TEncodingForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if (ModalResult = mrOk) and Assigned(Tree.FocusedNode) then
    FSelected := FArray[Tree.FocusedNode^.Index].CodePage;

  CanClose := FSelected <> 0;
end;

procedure TEncodingForm.RebuildTree;
begin
  Tree.Clear;
  Tree.RootNodeCount := Length(FArray);
end;

procedure TEncodingForm.TreeGetImageIndex(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
  var Ghosted: Boolean; var ImageIndex: TImageIndex);
begin
  if Kind in [ikNormal, ikSelected] then ImageIndex := 0;
end;

procedure TEncodingForm.TreeGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
begin
  if TextType = ttNormal then
    CellText := FArray[Node^.Index].Display;
end;

function IsMatch(const Filter: string; Data: PEncodingRec): Boolean; inline;
begin
  Result := (Length(Filter) = 0)
    or ContainsText(Data^.Name, Filter)
    or ContainsText(Data^.Display, Filter)
    or ContainsText(IntToStr(Data^.CodePage), Filter);
end;

procedure TEncodingForm.TreeInitNode(Sender: TBaseVirtualTree; ParentNode,
  Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
  var Data: PEncodingRec := @FArray[Node^.Index];
  if Data^.CodePage = FSelected then
    Include(InitialStates, ivsSelected);

  // filter
  if not IsMatch(FFilter, Data) then Include(InitialStates, ivsFiltered);
end;

procedure TEncodingForm.TreeNodeDblClick(Sender: TBaseVirtualTree;
  const HitInfo: THitInfo);
begin
  if Assigned(HitInfo.HitNode) then
    ModalResult := mrOk;
end;

procedure TEncodingForm.FilterEditChange(Sender: TObject);
begin
  FilterTimer.Enabled := False;
  FilterTimer.Enabled := True;
end;

procedure TEncodingForm.FilterTimerTimer(Sender: TObject);
begin
  FFilter := Trim(FilterEdit.Text);

  var Input: Integer;
  if TryStrToInt(FFilter, Input) then
    FSelected := Input
  else
    FSelected := 0;

  FilterTimer.Enabled := False;
  RebuildTree;
end;

end.
